package fr.lifl.homade.iuta.model;


import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import java.util.Stack;

import fr.lifl.homade.iuta.exceptions.InsufisantDataException;
import fr.lifl.homade.iuta.ip.IP;
import fr.lifl.homade.iuta.ip.IPAdd;
import fr.lifl.homade.iuta.ip.IPAnd;
import fr.lifl.homade.iuta.ip.IPDec;
import fr.lifl.homade.iuta.ip.IPInc;
import fr.lifl.homade.iuta.ip.IPMinus;
import fr.lifl.homade.iuta.ip.IPNot;
import fr.lifl.homade.iuta.ip.IPOr;

/** 
 * L'interpreteur est charge de contenir les donnees d'un programme,
 * ainsi que ses instruction, qu'il interprete une fois le programme
 * lance
 */
public class Interpreteur extends Observable{
	private final Map<Integer, IP> IPMap;
	private final Stack<Integer> dataStack;
	private final Stack<String> callStack;

	public enum defaultIDs{
	}
	
	public Interpreteur(){
		IPMap = new HashMap<Integer, IP>();
		dataStack = new Stack<Integer>();
		callStack = new Stack<String>();

		/*
		 * Ajout des IP supportees avec leur ID par defaut
		 */
		IPMap.put(32, new IPAdd(this));
		IPMap.put(33, new IPMinus(this));
		IPMap.put(34, new IPInc(this));
		IPMap.put(35, new IPDec(this));
		IPMap.put(36, new IPNot(this));
		IPMap.put(37, new IPAnd(this));
		IPMap.put(38, new IPOr(this));
		//...
	}


	/**
	 * Fonction principale, ajoute dans la callStack 
	 * l'identifiant ou l'adresse de la fonction
	 * pouvant interpreter la commande tapee 
	 * 
	 * @param instruction La commande a interpreter
	 * @throws InsufisantDataException Si il n'y a pas assez de donnees dans le stack de donnees
	 */
	public void interpreter(String instruction) throws InsufisantDataException{
		if(instruction.charAt(0) != '1')
			throw new IllegalArgumentException("L'instruction doit commencer par 1");
		if(instruction.length() != 16)
			throw new IllegalArgumentException("Longueur de l'instruction incorrecte");

		switch(instruction.charAt(0)){
		case '1':
			try {
				callIP(instruction);
			} catch (InsufisantDataException e) {
				throw new InsufisantDataException();
			}
			break;
		case '0':
			throw new UnsupportedOperationException();
		default:
			throw new IllegalArgumentException(); 
		}
	}

	/**
	 * Ajoute une instruction dans la callStack
	 * @param instruction l'instruction a ajouter
	 * @throws IllegalArgumentException Si la longueur de l'instruction ne vaut pas 16
	 */
	public void addInstruction(String instruction){
		if(instruction.length() != 16)
			throw new IllegalArgumentException("Longueur de l'instruction incorrecte, devrait valoir 16");
		
		callStack.add(instruction);
	}
	
	/**
	 * Appelle l'IP correspondant a l'instruction avec les parametres
	 * specifies
	 * @param instruction L'instruction a interpreter
	 * @throws InsufisantDataException Si il n'y a pas assez de donnees dans le stack de donnees
	 * @throws IllegalArgumentException Si l'instruction ne commence pas par 1
	 * @throws IllegalArgumentException Si la longueur de l'instruction ne vaut pas 16
	 */
	public void callIP(String instruction) throws InsufisantDataException{
		if(instruction.charAt(0) != '1')
			throw new IllegalArgumentException("L'instruction doit commencer par 1");
		if(instruction.length() != 16)
			throw new IllegalArgumentException("Longueur de l'instruction incorrecte");
		
		String tmp;
		int nPop = 0, nPush = 0, nIP = 0;
		int beg, end;
		boolean shortIP = false;
		
		/* Detection du nombre de valeurs retirees de la dataStack vers l'IP */
		beg = 1; end = 3;
		tmp = instruction.substring(beg, end);
		nPop = Integer.parseInt(tmp, 2);

		/* Detection du nombre de valeurs ajoutees dans la dataStack */
		beg = 3; end = 5;
		tmp = instruction.substring(beg, end);
		nPush = Integer.parseInt(tmp, 2);

		/* Detection du Short_IP */
		shortIP = instruction.charAt(5) == '1' ? true : false; 

		/* Detection de l'IP */
		beg = 6; end = 16;
		tmp = instruction.substring(beg, end);
		nIP = Integer.parseInt(tmp, 2);

		try {
			IPMap.get(nIP).exec(nPop, nPush);
		} catch (InsufisantDataException e) {
			throw new InsufisantDataException();
		}
	}

	public void newIp(int index){
		throw new UnsupportedOperationException();
	}
	
	public void delIp(int index){
		throw new UnsupportedOperationException();
	}

	public static void main(String[] args){
		Interpreteur inter = new Interpreteur();

		try {
			inter.interpreter("1111110000100010");
		} catch (InsufisantDataException e) {
			// TODO Arret de l'interpretation, remise a 0 des instructions ?
			e.printStackTrace();
		}
//		Scanner sc = new Scanner(System.in);
//		while(true){
//			System.out.print("Instruction: ");
//			inter.interpreter(sc.nextLine());
//		}
	}


	public Map<Integer, IP> getIPMap() {
		return IPMap;
	}


	public Stack<Integer> getDataStack() {
		return dataStack;
	}


	public Stack<String> getCallStack() {
		return callStack;
	}
}
